package p1gu;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

/**
 * Framework that controls the game (Game.java) that created it, update it and
 * draw it on the screen.
 *
 * @author www.gametutorial.net
 */
public class Framework extends Canvas {

    /**
     * Width of the frame.
     */
    public static int frameWidth;
    /**
     * Height of the frame.
     */
    public static int frameHeight;
    /**
     * Time of one second in nanoseconds. 1 second = 1 000 000 000 nanoseconds
     */
    public static final long secInNanosec = 1000000000L;
    /**
     * Time of one millisecond in nanoseconds. 1 millisecond = 1 000 000
     * nanoseconds
     */
    public static final long milisecInNanosec = 1000000L;
    /**
     * FPS - Frames per second How many times per second the game should update?
     */
    private final int GAME_FPS = 60;
    /**
     * Pause between updates. It is in nanoseconds.
     */
    private final long GAME_UPDATE_PERIOD = secInNanosec / GAME_FPS;

    /**
     * Possible states of the game
     */
    public static enum GameState {

        PLAYING, STARTING, VISUALIZING, GAME_CONTENT_LOADING, MAIN_MENU, OPTIONS, GAMEOVER, DESTROYED
    }
    /**
     * Current state of the game
     */
    public static GameState gameState;
    /**
     * Elapsed game time in nanoseconds.
     */
    private long gameTime;
    // It is used for calculating elapsed time.
    private long lastTime;
    // The actual game
    private Game game;
    //The actual menu
    private Menu menu;

    public Framework() {
        super();

        gameState = GameState.VISUALIZING;

        //We start game in new thread.
        Thread gameThread = new Thread("GameLoop") {
            @Override
            public void run() {
                GameLoop();
            }
        };
        gameThread.start();
    }

    /**
     * Set variables and objects. This method is intended to set the variables
     * and objects for this class, variables and objects for the actual game can
     * be set in Game.java.
     */
    private void Initialize() {
        menu = new Menu(this);
        

    }

    /**
     * Load files - images, sounds, ... This method is intended to load files
     * for this class, files for the actual game can be loaded in Game.java.
     */
    private void LoadContent() {
    }

    /**
     * In specific intervals of time (GAME_UPDATE_PERIOD) the game/logic is
     * updated and then the game is drawn on the screen.
     */
    private void GameLoop() {
        // This two variables are used in VISUALIZING state of the game. We used them to wait some time so that we get correct frame/window resolution.
        long visualizingTime = 0, lastVisualizingTime = System.nanoTime();

        // This variables are used for calculating the time that defines for how long we should put threat to sleep to meet the GAME_FPS.
        long beginTime, timeTaken, timeLeft;

        while (true) {
            beginTime = System.nanoTime();

            switch (gameState) {
                case PLAYING:
                    gameTime = System.currentTimeMillis()- lastTime;

                    game.UpdateGame(gameTime, mousePosition());

                    lastTime = System.currentTimeMillis();
                    break;
                case GAMEOVER:
                    //...
                    break;
                case MAIN_MENU:
                    menu.UpdateMenu(gameTime, mousePosition());
                    break;
                case OPTIONS:
                    //...
                    break;
                case GAME_CONTENT_LOADING:
                    lastTime = System.currentTimeMillis();
                    break;
                case STARTING:
                    // Sets variables and objects.
                    Initialize();
                    // Load files - images, sounds, ...
                    LoadContent();

                    // When all things that are called above finished, we change game status to main menu.
                    
                    break;
                case VISUALIZING:
                    // On Ubuntu OS (when I tested on my old computer) this.getWidth() method doesn't return the correct value immediately (eg. for frame that should be 800px width, returns 0 than 790 and at last 798px). 
                    // So we wait one second for the window/frame to be set to its correct size. Just in case we
                    // also insert 'this.getWidth() > 1' condition in case when the window/frame size wasn't set in time,
                    // so that we although get approximately size.
                    if (this.getWidth() > 1 && visualizingTime > secInNanosec) {
                        frameWidth = this.getWidth();
                        frameHeight = this.getHeight();

                        // When we get size of frame we change status.
                        gameState = GameState.STARTING;
                    } else {
                        visualizingTime += System.nanoTime() - lastVisualizingTime;
                        lastVisualizingTime = System.nanoTime();
                    }
                    break;
            }

            // Repaint the screen.
            repaint();

            // Here we calculate the time that defines for how long we should put threat to sleep to meet the GAME_FPS.
            timeTaken = System.nanoTime() - beginTime;
            timeLeft = (GAME_UPDATE_PERIOD - timeTaken) / milisecInNanosec; // In milliseconds
            // If the time is less than 10 milliseconds, then we will put thread to sleep for 10 millisecond so that some other thread can do some work.
            if (timeLeft < 10) {
                timeLeft = 10; //set a minimum
            }
            try {
                //Provides the necessary delay and also yields control so that other thread can do work.
                Thread.sleep(timeLeft);
            } catch (InterruptedException ex) {
            }
        }
    }

    /**
     * Draw the game to the screen. It is called through repaint() method in
     * GameLoop() method.
     */
    @Override
    public void Draw(Graphics2D g2d) {
        switch (gameState) {
            case PLAYING:
                game.Draw(g2d, mousePosition());
                break;
            case GAMEOVER:
                //...
                break;
            case MAIN_MENU:
                menu.Draw(g2d, mousePosition());
                break;
            case OPTIONS:
                //...
                break;
            case GAME_CONTENT_LOADING:
                //...
                break;
        }
    }

    /**
     * Starts new game.
     */
    public void newGame() {
        // We set gameTime to zero and lastTime to current time for later calculations.
        gameTime = 0;
        lastTime = System.nanoTime();

        game = new Game();
    }

    /**
     * Restart game - reset game time and call RestartGame() method of game
     * object so that reset some variables.
     */
    public void restartGame() {
        // We set gameTime to zero and lastTime to current time for later calculations.
        gameTime = 0;
        lastTime = System.nanoTime();

        game.RestartGame();

        // We change game status so that the game can start.
        gameState = GameState.PLAYING;
    }

    /**
     * Returns the position of the mouse pointer in game frame/window. If mouse
     * position is null than this method return 0,0 coordinate.
     *
     * @return Point of mouse coordinates.
     */
    private Point mousePosition() {
        try {
            Point mp = this.getMousePosition();

            if (mp != null) {
                return this.getMousePosition();
            } else {
                return new Point(0, 0);
            }
        } catch (Exception e) {
            return new Point(0, 0);
        }
    }

    /**
     * This method is called when keyboard key is released.
     *
     * @param e KeyEvent
     */
    @Override
    public void keyReleasedFramework(KeyEvent e) {

        switch (gameState) {
            case PLAYING:

                break;
            case GAMEOVER:
                //...
                break;
            case MAIN_MENU:

                menu.keyReleadedFramework(e);
                break;
            case OPTIONS:
                //...
                break;
            case GAME_CONTENT_LOADING:
                //...
                break;
        }

    }

    /**
     * This method is called when mouse button is clicked.
     *
     * @param e MouseEvent
     */
    @Override
    public void mouseClicked(MouseEvent e) {
        switch (gameState) {
            case PLAYING:
                game.mouseClicked( e);
                break;
            case GAMEOVER:
                //...
                break;
            case MAIN_MENU:

               
                break;
            case OPTIONS:
                //...
                break;
            case GAME_CONTENT_LOADING:
                //...
                break;
        }
    }
}
